<?php

/**
 * Walidator dostarczający wielu użytecznych metod do sprawdzania poprawności danych
 * 
 */
class Rapide_Validator
{
	private
		$_aRules = array();

	/**
	 * Dodaje regułę walidacji
	 * 
	 * Metodę stosuje się podczas zbiorowej walidacji wielu danych (np. przetwarzanie całego formularza)
	 *
	 * @param string $sParameter identyfikator parametru (np. nazwa pola z formularza)
	 * @param mixed $mValue wartość
	 * @param array $aCriteria tablica kryteriów walidacji
	 * @param string $sError zwracany tekst w przypadku błędnych danych
	 */
	public function addRule($sParameter, $mValue, array $aCriteria, $sError = null)
	{
		$this->_aRules[$sParameter] = array('value' => $mValue, 'criteria' => $aCriteria, 'error' => $sError);
	}

	/**
	 * Waliduje wszystkie dane zebrane za pomocą metody addRule()
	 * 
	 * @throws Rapide_Validator_Exception jeżeli podano nieznane kryterium walidacji
	 * @return array tabica asocjacyjna, gdzie klucz to identyfikator, a wartość wynosi 1 lub podany tekst błędu
	 */
	public function validate()
	{
		$aErrors = array();

		foreach($this->_aRules as $sParameter => $aRule)
		{
			$aCriteria = $aRule['criteria'];

			foreach($aCriteria as $sCriterion => $aParameters)
			{
				if(!array_key_exists('required', $aCriteria) && !$this->required($aRule['value']))
				{
					break;
				}
				
				// Jeżeli przekazano jedynie parametr i nie była to tablica,
				// utwórz tablicę z tym parametrem

				if(!is_array($aParameters))
				{
					$aParameters = array($aParameters);
				}

				$sError = '';

				if(array_key_exists('error', $aParameters))
				{
					$sError = $aParameters['error'];
					unset($aParameters['error']);
				}

				// Dodaj wartość do tablicy

				array_unshift($aParameters, $aRule['value']);

				// Sprawdź, czy podane kryterium istnieje

				if(method_exists($this, $sCriterion))
				{
					// Jeżeli dane nie przeszły poprawnie walidacji,
					// dodaj tekst błędy (lub true) do tablicy

					if(!call_user_func_array(array($this, $sCriterion), $aParameters))
					{
						$aErrors[$sParameter] = !empty($sError) ? $sError : ($aRule['error'] ? $aRule['error'] : true);
						break;
					}
				}
				else
				{
					// Rzuć wyjątkiem

					$sError = sprintf('Validator for criterion %s does not exist', $sCriterion);
					throw new Rapide_Validator_Exception($sError);
				}
			}
		}

		return $aErrors;
	}

	/**
	 * Sprawdza, czy przekazano wymagane dane
	 * 
	 * To kryterium jest sprawdzane podczas procesu walidacji zbiorowych danych.
	 * Jeżeli kryterium required nie zostało ustawione, pozostałe kryteria będą
	 * sprawdzane tylko w przypadku podania danych.
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function required($sString)
	{
		$sString = trim($sString);

		return strlen($sString) > 0;
	}

	/**
	 * Sprawdza, czy łańcuch zawiera tylko znaki alfa
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function alpha($sString)
	{
		return (bool)preg_match('/^[a-zA-Z]+$/', strval($sString));
	}

	/**
	 * Sprawdza, czy łańcuch zawiera tylko znaku numeryczne
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function numeric($sString)
	{
		return !preg_match('/^\s+/', strval($sString)) && is_numeric($sString);
	}

	/**
	 * Sprawdza, czy łańcuch zawiera tylko dane alfanumeryczne
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function alphanumeric($sString)
	{
		return (bool)preg_match('/^[a-zA-Z0-9]+$/', strval($sString));
	}

	/**
	 * Sprawdza, czy łańuch zawiera znaki alfa i numeryczne
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function alphaandnumeric($sString)
	{
		return (bool)preg_match('/[0-9]*[a-zA-Z]+[0-9]+[a-zA-Z]*/', strval($sString));
	}

	/**
	 * Sprawdza, czy łańcuch zawiera znaki alfa
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function withAlpha($sString)
	{
		$sExpression = '/[a-zA-Z]+/';
		return (bool)preg_match($sExpression, strval($sString));
	}

	/**
	 * Sprawdza, czy łańcuch zawiera znaki numeryczne
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function withNumeric($sString)
	{
		$sExpression = '/[0-9]+/';
		return (bool)preg_match($sExpression, strval($sString));
	}

	/**
	 * Sprawdza, czy łańcuch jest poprawną liczbą całkowitą
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function int($sString)
	{
		return !preg_match('/^\s+/', strval($sString)) && (strval(intval($sString)) == $sString);
	}

	/**
	 * Sprawdza, czy łańcuch jest poprawną liczbą całkowitą bez znaku
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function uint($sString)
	{
		return !preg_match('/^\s+/', strval($sString)) && (strval(abs(intval($sString))) == $sString);
	}

	/**
	 * CSprawdza, czy łańcuch jest poprawną liczbą zmiennoprzecinkową
	 * 
	 * @param string $sString
	 * @return bool
	 */
	public function float($sString)
	{
		return !preg_match('/^\s+/', strval($sString)) && (strval(floatval($sString)) == $sString);
	}

	/**
	 * Sprawdza, czy długość łańcucha nie jest mniejsza od podanej wartości
	 * 
	 * @throws Rapide_Validator_Exception jeżeli pierwszy parametr nie jest łańcuchem
	 * @param string $sString
	 * @param int $iMin
	 * @return bool
	 */
	public function min($sString, $iMin)	
	{
		if(is_string($sString))
		{
			return strlen($sString) >= (int)$iMin;
		}
		else
		{
			$sError = 'First parameter must be a string';
			throw new Rapide_Validator_Exception($sError);
		}
	}

	/**
	 * Sprawdza, czy wartość numeryczna nie jest mniejsza od podanej
	 * 
	 * @throws Rapide_Validator_Exception jeżeli pierwszy parametr nie jest liczbą
	 * @param string $sString
	 * @param mixed $mMin
	 * @return bool
	 */
	public function minn($mValue, $mMin)
	{
		if(is_numeric($mValue)) {
			return $mValue >= $mMin;
		}
		else
		{
			$sError = 'First parameter must be a numeric';
			throw new Rapide_Validator_Exception($sError);
		}
	}

	/**
	 * Sprawdza, czy długość łańcucha nie jest większa od podanej wartości
	 * 
	 * @throws Rapide_Validator_Exception jeżeli pierwszy parametr nie jest łańcuchem
	 * @param string $sString
	 * @param int $iMax
	 * @return bool
	 */
	public function max($sString, $iMax)
	{
		if(is_string($sString))
		{
			return strlen($sString) <= (int)$iMax;
		}
		else
		{
			$sError = 'First parameter must be a string';
			throw new Rapide_Validator_Exception($sError);
		}
	}

	/**
	 * Sprawdza, czy wartość numeryczna nie jest większa od podanej
	 * 
	 * @throws Rapide_Validator_Exception jeżeli pierwszy parametr nie jest liczbą
	 * @param string $sString
	 * @param mixed $mMax
	 * @return bool
	 */
	public function maxn($mValue, $mMax)
	{
		if(is_numeric($mValue)) {
			return $mValue <= $mMax;
		}
		else
		{
			$sError = 'First parameter must be a numeric';
			throw new Rapide_Validator_Exception($sError);
		}
	}

	/**
	 * Sprawdza, czy długość łańcucha zawiera się pomiędzy podanymi wartościami
	 * 
	 * @throws Rapide_Validator_Exception jeżeli pierwszy parametr nie jest łańcuchem
	 * @param string $sString
	 * @param int $iMin
	 * @param int $iMax
	 * @return bool
	 */
	public function between($sString, $iMin, $iMax)
	{
		return $this->min($sString, $iMin) && $this->max($sString, $iMax);
	}

	/**
	 * Sprawdza, czy liczba zawiera się między podanymi wartościami
	 * 
	 * @throws Rapide_Validator_Exception if first parameter is not a numeric
	 * @param string $sString
	 * @return bool
	 */
	public function betweenn($mValue, $iMin, $iMax)
	{
		return $this->minn($mValue, $iMin) && $this->maxn($mValue, $iMax);
	}

	/**
	 * Sprawdza, czy długość łańcucha wynosi dokładnie tyle, co podana wartość
	 *
	 * @param string $sString
	 * @param int $iLength
	 * @return bool
	 */
	public function exact($sString, $iLength)
	{
		return (strlen($sString) == $iLength);
	}

	/**
	 * Sprawdza, czy podane łańcuchy są takie same
	 *
	 * @param string $sString
	 * @param string $sValue
	 * @return bool
	 */
	public function equal($sString, $sValue)
	{
		return ($sString == $sValue);
	}

	/**
	 * Sprawdza, czy podana data jest poprawna
	 * 
	 * Metoda przyjmuje format daty YYYY-MM-DD.
	 * Ponadto sprawdza, czy data istnieje w kalendarzu.
	 *
	 * @param string $sString
	 * @return date
	 */
	public function date($sString)
	{
		// YYYY-MM-DD
		$sExpression = '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/';

		if(!(bool)preg_match($sExpression, strval($sString)))
		{
			return false;
		}

		$iTime = strtotime($sString);
		
		return checkdate(date('m', $iTime), date('d', $iTime), date('Y', $iTime));
	}

	/**
	 * Sprawdza, czy łańcuch nie równa się żadnej z podanych wartości
	 *
	 * @param string $sString
	 * @param array $aEntries tablica wartości
	 * @return bool
	 */
	public function isNot($sString, array $aEntries)
	{
		foreach($aEntries as $sEntry)
		{
			if(strval($sString) === strval($sEntry))
				return false;
		}

		return true;
	}

	/**
	 * Sprawdza, czy łańcuch równa się przynajmniej jednej z podanych wartości
	 *
	 * @param string $sString
	 * @param array $aEntries tablica wartości
	 * @return bool
	 */
	public function isAny($sString, array $aEntries)
	{
		foreach($aEntries as $sEntry)
		{
			if(strval($sString) === strval($sEntry))
				return true;
		}

		return false;
	}

	/**
	 * TODO: domyślne kryteria walidacji loginu
	 *
	 * @param string $sString
	 */
	public function login($sString) // alnum + (., -, _)
	{
	}

	/**
	 * Sprawdza, czy podany e-mail jest prawidłowy
	 *
	 * @param string $sString
	 * @return bool
	 */
	public function email($sString)
	{
		$sExpression = '/^[a-zA-Z]+([\-_\.]*[a-zA-Z0-9]+)*@([a-zA-Z0-9]+([\-_]+[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,6}$/';

		return (bool)preg_match($sExpression, strval($sString));
	}

	/**
	 * Sprawdza, czy podany adres Skype jest prawidłowy
	 *
	 * @param string $sString
	 * @return bool
	 */
	public function skype($sString)
	{
		$sExpression = '/^[a-zA-Z]+([\-_\.]*[a-zA-Z0-9]+)*$/';
		return (bool)preg_match($sExpression, strval($sString));
	}

	/**
	 * Sprawdza, czy podany adres jest prawidłowym URL do zasobów WWW
	 *
	 * @param string $sString
	 * @return bool
	 */
	public function www($sString)
	{
		$sExpression = '/^(http(s)?\:\/\/)?(www\.)?([a-zA-Z0-9]+([\-_]+[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,3}(\:[0-9]{1,5})?\/?(\/.+)*$/';
		return (bool)preg_match($sExpression, strval($sString));
	}

	/**
	 * Sprawdza, czy łańcuch spełnia podane wyrażenie regularne
	 * 
	 * Metoda stosuje funkcję preg_match. Należy pamiętać o znacznikach wyrażenia '/'.
	 *
	 * @param string $sString
	 * @param string $sExpression
	 * @return bool
	 */
	public function match($sString, $sExpression)
	{
		return (bool)preg_match($sExpression, strval($sString));
	}
}

?>